import { Game } from '../objects/Game';
import { Move } from '../objects/Move';
import { opponentColor, Piece, PieceColor, PieceType } from '../objects/Piece';
import { GameState, isThreatEqual, Judge } from '../objects/Judge';
import { Board } from '../objects/Board';

type ScoreTable = {
    [key in PieceType]: number;
};

export class GeneratedMove extends Move {
    constructor(row1: number, col1: number, row2: number, col2: number, giveup: boolean, score: number) {
        super(row1, col1, row2, col2);
        this.giveUp = giveup;
        this.score = score;
    }

    public static fromMove(move: Move, score: number): GeneratedMove {
        return new GeneratedMove(move.sourceRow, move.sourceCol, move.targetRow, move.targetCol, false, score);
    }

    public readonly giveUp;
    public readonly score;
}

export class ChessPlayer {
    constructor(game: Game, color: PieceColor) {
        this.game = game;
        this.color = color;
        this.lastMovedPiece = undefined;
    }

    public makeNextMove(): GeneratedMove {
        const judge = new Judge(this.game.board, this.game.rules);
        const allPossibleMoves = this.game.board.getPiecesOfColor(this.color)
            .map(it => judge.enumerateAllValidMoves(it))
            .reduce((a, v) => a.concat(v), []);
        // greedy
        const moveScores = allPossibleMoves
            .filter(it => !it.getSourcePiece(this.game.board).equals(this.lastMovedPiece))
            .map(it => { return {
                board: this.game.board.clone(),
                move: it
            }}).map(it => this.scoreMove(it.board, it.move));
        const maxScore = moveScores.reduce((a, v) => Math.max(a, v), Number.NEGATIVE_INFINITY);
        const viableMoves = moveScores.map(it => it === maxScore).map((it, key) => {
            return {
                value: it,
                index: key
            }
        }).filter(it => it.value);
        if (viableMoves.length < 1) {
            return new GeneratedMove(0, 0, 0, 0, true, -1);
        } else {
            const move = allPossibleMoves[viableMoves[Math.floor(Math.random() * viableMoves.length)].index];
            this.lastMovedPiece = move.getSourcePiece(this.game.board);
            return GeneratedMove.fromMove(move, maxScore);
        }
    }

    private scoreMove(board: Board, move: Move): number {
        let score = 0;
        const target = move.getTargetPiece(board);
        const judge = new Judge(board, this.game.rules);
        const currentThreatState = judge.analyze(this.color);
        const currentOpponentThreatState = judge.analyze(opponentColor(this.color));
        const beforeState = board.save();
        board.tryMove(move);
        const afterState = board.save();
        const afterThreatState = judge.analyze(this.color);
        const afterOpponentThreatState = judge.analyze(opponentColor(this.color));
        // capturing a piece = +
        // losing a piece after doing so = -
        // resolving a threat = + * 0.8
        // creating a threat to opponent = + * 0.8
        // creating a threat to self = - * 1.1
        // and move shall not create a check for self
        if (afterThreatState.state > GameState.NORMAL) {
            return -10000;
        }
        // if there is a check, resolve it first
        if (currentThreatState.state === GameState.CHECK && afterThreatState.state === GameState.NORMAL) {
            return 10000;
        }
        score += this.scoreTable[target.type];
        const eliminatedThreats = currentThreatState.threats
            .filter(it => afterThreatState.threats.findIndex(v => isThreatEqual(it, v)) < 0);
        const createdThreats = afterThreatState.threats
            .filter(it => currentThreatState.threats.findIndex(v => isThreatEqual(it, v)) < 0);
        board.load(beforeState);
        score += eliminatedThreats.map(it => this.scoreTable[board.getPieceAt(it.position.row, it.position.col).type])
            .reduce((a, v) => a + v, 0) * 0.8;
        board.load(afterState);
        score -= createdThreats.map(it => this.scoreTable[board.getPieceAt(it.position.row, it.position.col).type])
            .reduce((a, v) => a + v, 0) * 1.1;
        const createdOpponentThreats = afterOpponentThreatState.threats
            .filter(it => currentOpponentThreatState.threats.findIndex(v => isThreatEqual(it, v)) < 0);
        score += createdOpponentThreats.map(it => this.scoreTable[board.getPieceAt(it.position.row, it.position.col).type])
            .reduce((a, v) => a + v, 0) * 0.8;
        return score;
    }

    private readonly game: Game;
    private readonly color: PieceColor;
    private readonly scoreTable: ScoreTable = {
        ".": 0, a: 200, b: 90, c: 95, g: 1000, k: 90, p: 10, r: 100
    };
    private lastMovedPiece: Piece | undefined;
}
